#include <buola/buola.h>

#if BUOLA_PLATFORM_LINUX
#include <buola/cv/io/cwebcam.h>
#include <buola/cv/barcode/scan.h>
#include <buola/cv/barcode/decode.h>
#include <buola/app/ceventloop.h>
#include <buola/image/io.h>
#include <buola/image/cgc.h>
#include <buola/app/ccmdline.h>
#include <buola/app/capp.h>
#include <buola/gui/ctoolkit.h>
#include <buola/widgets/cimagebox.h>
#include <zbar.h>
#include <fftw3.h>

using namespace buola;

extern "C"
{

void zbar_scanner_get_state (const zbar::zbar_scanner_t *scn,
                             unsigned *x,
                             unsigned *cur_edge,
                             unsigned *last_edge,
                             int *y0,
                             int *y1,
                             int *y2,
                             int *y1_thresh);
}

static int sY=0;
static int sFound=0;
static int sCount=0;
img::CImage_rgb8 sOldImage;
img::CImage_gray8 sOldGray;

#ifndef ZBAR_FIXED
# define ZBAR_FIXED 5
#endif
#define ROUND (1 << (ZBAR_FIXED - 1))

#ifndef ZBAR_SCANNER_THRESH_INIT_WEIGHT
# define ZBAR_SCANNER_THRESH_INIT_WEIGHT .44
#endif
#define THRESH_INIT ((unsigned)((ZBAR_SCANNER_THRESH_INIT_WEIGHT       \
                                 * (1 << (ZBAR_FIXED + 1)) + 1) / 2))

#ifndef ZBAR_SCANNER_THRESH_FADE
# define ZBAR_SCANNER_THRESH_FADE 8
#endif

#ifndef ZBAR_SCANNER_EWMA_WEIGHT
# define ZBAR_SCANNER_EWMA_WEIGHT .78
#endif
#define EWMA_WEIGHT ((unsigned)((ZBAR_SCANNER_EWMA_WEIGHT              \
                                 * (1 << (ZBAR_FIXED + 1)) + 1) / 2))

static constexpr double cEWMAWeight_d=0.78;
static constexpr int cEWMAWeight=(int)(cEWMAWeight_d*(1<<ZBAR_FIXED)+0.5);

void scan_line(const img::CMView_gray8 &pImg,int pY,zbar::zbar_decoder_t *pDecoder)
{
    std::vector<int> lWidths;
    cvi::barcode::scan_line(pImg.RawData(0,pY),1,pImg.Width(),lWidths);

    zbar::zbar_decoder_reset(pDecoder);
    zbar::zbar_decoder_new_scan(pDecoder);
    for(int i=0;i<lWidths.size();i++)
    {
        zbar::zbar_decode_width(pDecoder,lWidths[i]);
    }
}

void scan_line_vec(const img::CMView_gray8 &pImg,int pY,zbar::zbar_decoder_t *pDecoder)
{
    std::vector<int> lWidths;
    cvi::barcode::scan_line(pImg.RawData(0,pY),1,pImg.Width(),lWidths);
    std::vector<cvi::barcode::CSymbol> lSyms;
    cvi::barcode::decode_i25(lWidths,lSyms);
    cvi::barcode::decode_ean(lWidths,lSyms);
    cvi::barcode::decode_code128(lWidths,lSyms);
    cvi::barcode::decode_code39(lWidths,lSyms);
    
    for(const auto &s : lSyms)
    {
        sFound++;
        msg_info() << pY << " " << (int)s.mType << " " << s.mText << " " << s.mDirection << "\n";
        img::CImage_rgb8 *lImage=(img::CImage_rgb8*)zbar_decoder_get_userdata(pDecoder);
//        for(int i=0;i<lImage->Width();i++)
        for(int i=s.mB;i<s.mE;i++)
        {
            (*lImage)(i,pY)[0]=0;
            (*lImage)(i,pY)[2]=0;
        }
    }
}

static void symbol_handler(zbar::zbar_decoder_t *pDecoder)
{
    using namespace zbar;
    if(zbar_decoder_get_type(pDecoder)<=1) return;
    msg_info() << "symbol found :" << (int)zbar_decoder_get_type(pDecoder) << "\n";
    std::string lSymbol(zbar_decoder_get_data(pDecoder),zbar_decoder_get_data_length(pDecoder));
    msg_info() << sY << " " << lSymbol << "\n";
    sFound++;
  
    img::CImage_rgb8 *lImage=(img::CImage_rgb8*)zbar_decoder_get_userdata(pDecoder);
    for(int i=0;i<lImage->Width();i++)
    {
        (*lImage)(i,sY)[1]=0;
        (*lImage)(i,sY)[2]=0;
    }
}

void OnImage(const img::CImage_rgb8 &pImage,gui::CImageBox &pBox)
{
    auto lImage=clone(pImage);
    //here we can do normal processing
    /* timestamp image
     * FIXME prefer video timestamp
     */

    img::CImage_gray8 lGray=img::convert<img::TFormat_gray8>(lImage);
    
    using namespace zbar;
    
    zbar_decoder_t *lDecoder=zbar_decoder_create();
    zbar_decoder_set_handler(lDecoder,symbol_handler);
    zbar_decoder_set_userdata(lDecoder,&lImage);
//    zbar_decoder_set_config(lDecoder,ZBAR_NONE,ZBAR_CFG_ENABLE,0);
    zbar_decoder_set_config(lDecoder,ZBAR_QRCODE,ZBAR_CFG_ENABLE,0);
    zbar_decoder_set_config(lDecoder,ZBAR_PDF417,ZBAR_CFG_ENABLE,0);
//     zbar_decoder_set_config(lDecoder,ZBAR_I25,ZBAR_CFG_ENABLE,1);
//      zbar_decoder_set_config(lDecoder,ZBAR_EAN8,ZBAR_CFG_ENABLE,1);
//      zbar_decoder_set_config(lDecoder,ZBAR_EAN13,ZBAR_CFG_ENABLE,1);
//      zbar_decoder_set_config(lDecoder,ZBAR_UPCE,ZBAR_CFG_ENABLE,1);
//      zbar_decoder_set_config(lDecoder,ZBAR_UPCA,ZBAR_CFG_ENABLE,1);
//      zbar_decoder_set_config(lDecoder,ZBAR_ISBN10,ZBAR_CFG_ENABLE,1);
//      zbar_decoder_set_config(lDecoder,ZBAR_ISBN13,ZBAR_CFG_ENABLE,1);
//     zbar_decoder_set_config(lDecoder,ZBAR_CODE39,ZBAR_CFG_ENABLE,1);
//      zbar_decoder_set_config(lDecoder,ZBAR_CODE128,ZBAR_CFG_ENABLE,1);
    zbar_scanner_t *lScanner=zbar_scanner_create(lDecoder);

    int lOldFound=sFound;
    sFound=0;
    msg_info() << "frame!!\n";
    int lYDensity=1;
    sFound=0;
    start_timer();
    for(int y=0;y<lImage.Height();y+=lYDensity)
    {
        sY=y;
        scan_line(lGray,y,lDecoder);
//        scan_line_vec(lGray,y,lDecoder);
        ///\todo more directions
    }
    end_timer();
    msg_info() << sFound << "\n";
    sFound=0;
    start_timer();
//    for(int y=340;y<341;y+=lYDensity)
    for(int y=0;y<lImage.Height();y+=lYDensity)
    {
        sY=y;
        scan_line_vec(lGray,y,lDecoder);
        ///\todo more directions
    }
    end_timer();
    msg_info() << sFound << "\n";
    
    if(sFound>40)
    {
//        img::save(lImage,io::file("color"+to_string(sCount)+".pgm"));
//        img::save(lGray,io::file("gray"+to_string(sCount)+".pgm"));
//         img::save(sOldImage,io::file("color"+to_string(sCount)+"o.png"));
//         img::save(sOldGray,io::file("gray"+to_string(sCount)+"o.png"));
        sCount++;
    }
    else
    {
        sFound=std::max(sFound,lOldFound);
    }
    
#if 0
    /* FIXME tmp hack to filter bad EAN results */
    if(syms->nsyms && !iscn->enable_cache &&
       (density == 1 || CFG(iscn, ZBAR_CFG_Y_DENSITY) == 1)) {
        zbar_symbol_t **symp = &syms->head, *sym;
        while((sym = *symp)) {
            if(sym->type < ZBAR_I25 && sym->type > ZBAR_PARTIAL &&
               sym->quality < 3) {
                /* recycle */
                *symp = sym->next;
                syms->nsyms--;
                sym->next = NULL;
                _zbar_image_scanner_recycle_syms(iscn, sym);
            }
            else
                symp = &sym->next;
        }
    }

    if(syms->nsyms && iscn->handler)
        iscn->handler(img, iscn->userdata);
#endif
    zbar_scanner_destroy(lScanner);
    zbar_decoder_destroy(lDecoder);

    sOldImage=clone(lImage);
    sOldGray=std::move(lGray);
    
    {
        lock_guard<recursive_mutex> lLock(get_main_event_loop().GetMutex());
        pBox.SetImage(std::move(lImage));
    }
}

int main(int pNArg,char **pArgs)
{
    buola_init(pNArg,pArgs);

    try
    {
        CApp lApp;
        
        gui::get_toolkit()->GetOpenGLGC()->MakeCurrent();

        //create the scene
        gui::PImageBox lBox=new gui::CImageBox;
        lBox->Map(CSize_d(800,600));
        
        cvi::CWebCam lWebCam(io::file("/dev/video0"));
        lWebCam.sImage.Connect([&](const img::CImage_rgb8 &pImage){   OnImage(pImage,*lBox);  });

        lWebCam.Start();
//        img::CImage_rgb8 lImage=img::load<img::CImage_rgb8>(io::get_home()/"barcode39.pgm");
//        OnImage(lImage,*lBox);
        
        lApp.Run();
    }
    catch(std::exception &pE)
    {
        msg_info() << "caught exception " << pE.what() << "\n";
    }

    return buola_finish();
}

#else
int main() {}
#endif
